﻿using System;
using System.Collections.Generic;
using TLang.Values;
using System.Linq;

namespace TLang
{
    using Ast;

    public class Binder
    {
        public static void Define(Node pattern, TType valueType, Value value, Scope env)
        {
            if (pattern is NameNode)
            {
                String id = ((NameNode)pattern).Id;
                Value v = env.LookupLocal(id);
                if (v != null)
                {
                    throw Util.GeneralError(pattern, "trying to redefine name: " + id);
                }
                else
                {
                    if (valueType != null)
                    {
                        env.PutTypedValue(id, value, valueType);
                    }
                    else
                    {
                        env.PutValue(id, value);
                    }
                }
            }
            else if (pattern is RecordLiteralNode)
            {
                if (value is RecordType)
                {
                    Map<String, Node> patterns = ((RecordLiteralNode)pattern).Map;
                    Scope values = ((RecordType)value).Properties;
                    if (patterns.Keys.Count == values.KeysLocal.Count && values.KeysLocal.All(a => patterns.Keys.Contains(a)))
                    {
                        foreach (String k1 in patterns.Keys)
                        {
                            Define(patterns.Get(k1), null/*TODO:*/, values.LookupLocal(k1), env);
                        }
                    }
                    else
                    {
                        throw Util.GeneralError(pattern, "define with records of different attributes: " +
                                patterns.Keys + " v.s. " + values.KeysLocal);
                    }
                }
                else
                {
                    throw Util.GeneralError(pattern, "define with incompatible types: record and " + value);
                }
            }
            else if (pattern is VectorLiteralNode)
            {
                if (value is VectorValue)
                {
                    List<Node> elms1 = new List<Node>();
                    foreach (var node in ((VectorLiteralNode)pattern).Elements)
                    {
                        if (!(node is KeywordNode))
                        {
                            elms1.Add(node);
                        }
                    }
                    VectorValue elms2 = ((VectorValue)value);
                    if (elms1.Count == elms2.Count)
                    {
                        for (int i = 0; i < elms1.Count; i++)
                        {
                            Define(elms1[i], null/*TODO:*/, elms2[i], env);
                        }
                    }
                    else
                    {
                        throw Util.GeneralError(pattern,
                                "define with vectors of different sizes: " + elms1.Count + " v.s. " + elms2.Count);
                    }
                }
                else
                {
                    throw Util.GeneralError(pattern, "define with incompatible types: vector and " + value);
                }
            }
            else
            {
                throw Util.GeneralError(pattern, "unsupported pattern of define: " + pattern);
            }
        }


        public static void Set(Node pattern, Value value, Scope env)
        {
            if (pattern is NameNode)
            {
                String id = ((NameNode)pattern).Id;
                Scope d = env.FindDefiningScope(id);

                if (d == null)
                {
                    throw Util.GeneralError(pattern, "seted name was not defined: " + id);
                }
                else
                {
                    d.PutValue(id, value);
                }
            }
            else if (pattern is SubscriptNode)
            {
                ((SubscriptNode)pattern).Set(value, env);
            }
            else if (pattern is RecordLiteralNode)
            {
                if (value is RecordType)
                {
                    Map<String, Node> elms1 = ((RecordLiteralNode)pattern).Map;
                    Scope elms2 = ((RecordType)value).Properties;
                    if (elms1.Keys.Count == elms2.KeysLocal.Count && elms1.Keys.All(a => elms2.KeysLocal.Contains(a)))
                    {
                        foreach (String k1 in elms1.Keys)
                        {
                            Set(elms1.Get(k1), elms2.LookupLocal(k1), env);
                        }
                    }
                    else
                    {
                        throw Util.GeneralError(pattern, "set with records of different attributes: " +
                                elms1.Keys + " v.s. " + elms2.KeysLocal);
                    }
                }
                else
                {
                    throw Util.GeneralError(pattern, "set with incompatible types: record and " + value);
                }
            }
            else if (pattern is VectorLiteralNode)
            {
                if (value is VectorValue)
                {
                    List<Node> elms1 = ((VectorLiteralNode)pattern).Elements;
                    VectorValue elms2 = ((VectorValue)value);
                    if (elms1.Count == elms2.Count)
                    {
                        for (int i = 0; i < elms1.Count; i++)
                        {
                            Set(elms1[i], elms2[i], env);
                        }
                    }
                    else
                    {
                        throw Util.GeneralError(pattern, "set vectors of different sizes: " + elms1.Count + " v.s. " + elms2.Count);
                    }
                }
                else
                {
                    throw Util.GeneralError(pattern, "set incompatible types: vector and " + value);
                }
            }
            else
            {
                throw Util.GeneralError(pattern, "unsupported pattern of set: " + pattern);
            }
        }


        public static void CheckDup(Node pattern, Scope seen)
        {
            if (pattern is NameNode)
            {
                String id = ((NameNode)pattern).Id;
                if (seen.ContainsKeyLocal(id))
                {
                    throw Util.GeneralError(pattern, "duplicated name found in pattern: " + pattern);
                }
                else
                {
                    seen.PutProperties(id, new Map<string, object>());
                }
            }
            else if (pattern is RecordLiteralNode)
            {
                foreach (Node v in ((RecordLiteralNode)pattern).Map.Values)
                {
                    CheckDup(v, seen);
                }
            }
            else if (pattern is VectorLiteralNode)
            {
                foreach (Node v in ((VectorLiteralNode)pattern).Elements)
                {
                    CheckDup(v, seen);
                }
            }
        }
    }
}
